All files / core/view EventGenerator.ts

97.62% Statements 41/42
92.86% Branches 13/14
100% Functions 10/10
97.37% Lines 37/38
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195                                12x 12x 12x                         12x             305x         305x                                 1736x         1736x 1736x   1736x 1229x             17x                 1736x             1736x             1736x             1736x             1736x               1736x                         8680x             8680x   8680x 8680x 1246x       1246x 1521x 627x                           12x 1246x 568x   678x           678x                   12x 456x     456x 456x 456x   12x  
/**
 * Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
import { NamedNode, Node } from '../snap/Node';
import { Change } from './Change';
import { assertionError } from '@firebase/util';
import { Query } from '../../api/Query';
import { Index } from '../snap/indexes/Index';
import { EventRegistration } from './EventRegistration';
import { Event } from './Event';
 
/**
 * An EventGenerator is used to convert "raw" changes (Change) as computed by the
 * CacheDiffer into actual events (Event) that can be raised.  See generateEventsForChanges()
 * for details.
 *
 * @constructor
 */
export class EventGenerator {
  private index_: Index;
 
  /**
   *
   * @param {!Query} query_
   */
  constructor(private query_: Query) {
    /**
     * @private
     * @type {!Index}
     */
    this.index_ = this.query_.getQueryParams().getIndex();
  }
 
  /**
   * Given a set of raw changes (no moved events and prevName not specified yet), and a set of
   * EventRegistrations that should be notified of these changes, generate the actual events to be raised.
   *
   * Notes:
   *  - child_moved events will be synthesized at this time for any child_changed events that affect
   *    our index.
   *  - prevName will be calculated based on the index ordering.
   *
   * @param {!Array.<!Change>} changes
   * @param {!Node} eventCache
   * @param {!Array.<!EventRegistration>} eventRegistrations
   * @return {!Array.<!Event>}
   */
  generateEventsForChanges(
    changes: Change[],
    eventCache: Node,
    eventRegistrations: EventRegistration[]
  ): Event[] {
    const events: Event[] = [];
    const moves: Change[] = [];
 
    changes.forEach(change => {
      if (
        change.type === Change.CHILD_CHANGED &&
        this.index_.indexedValueChanged(
          change.oldSnap as Node,
          change.snapshotNode
        )
      ) {
        moves.push(
          Change.childMovedChange(
            change.childName as string,
            change.snapshotNode
          )
        );
      }
    });
 
    this.generateEventsForType_(
      events,
      Change.CHILD_REMOVED,
      changes,
      eventRegistrations,
      eventCache
    );
    this.generateEventsForType_(
      events,
      Change.CHILD_ADDED,
      changes,
      eventRegistrations,
      eventCache
    );
    this.generateEventsForType_(
      events,
      Change.CHILD_MOVED,
      moves,
      eventRegistrations,
      eventCache
    );
    this.generateEventsForType_(
      events,
      Change.CHILD_CHANGED,
      changes,
      eventRegistrations,
      eventCache
    );
    this.generateEventsForType_(
      events,
      Change.VALUE,
      changes,
      eventRegistrations,
      eventCache
    );
 
    return events;
  }
 
  /**
   * Given changes of a single change type, generate the corresponding events.
   *
   * @param {!Array.<!Event>} events
   * @param {!string} eventType
   * @param {!Array.<!Change>} changes
   * @param {!Array.<!EventRegistration>} registrations
   * @param {!Node} eventCache
   * @private
   */
  private generateEventsForType_(
    events: Event[],
    eventType: string,
    changes: Change[],
    registrations: EventRegistration[],
    eventCache: Node
  ) {
    const filteredChanges = changes.filter(change => change.type === eventType);
 
    filteredChanges.sort(this.compareChanges_.bind(this));
    filteredChanges.forEach(change => {
      const materializedChange = this.materializeSingleChange_(
        change,
        eventCache
      );
      registrations.forEach(registration => {
        if (registration.respondsTo(change.type)) {
          events.push(
            registration.createEvent(materializedChange, this.query_)
          );
        }
      });
    });
  }
 
  /**
   * @param {!Change} change
   * @param {!Node} eventCache
   * @return {!Change}
   * @private
   */
  private materializeSingleChange_(change: Change, eventCache: Node): Change {
    if (change.type === 'value' || change.type === 'child_removed') {
      return change;
    } else {
      change.prevName = eventCache.getPredecessorChildName(
        /** @type {!string} */
        change.childName,
        change.snapshotNode,
        this.index_
      );
      return change;
    }
  }
 
  /**
   * @param {!Change} a
   * @param {!Change} b
   * @return {number}
   * @private
   */
  private compareChanges_(a: Change, b: Change) {
    Iif (a.childName == null || b.childName == null) {
      throw assertionError('Should only compare child_ events.');
    }
    const aWrapped = new NamedNode(a.childName, a.snapshotNode);
    const bWrapped = new NamedNode(b.childName, b.snapshotNode);
    return this.index_.compare(aWrapped, bWrapped);
  }
}